#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/kobject.h>

struct my_kobj {   //内嵌kobject的结构
	int val;
	struct kobject kobj;
};

struct my_kobj *obj1, *obj2;
struct kobj_type my_type;

struct attribute name_attr = {
	.name = "name", //文件名
	.mode = 0444,  //指定文件的访问权限
};

struct attribute val_attr = {
	.name = "val", //文件名
	.mode = 0666, //指定文件的访问权限
};

struct attribute *my_attrs[] = {
	&name_attr, 
	&val_attr,
	NULL,
};

/*
结构体struct attribute里的name变量用来指定文件名，mode变量用来指定文件的访问权限。
这里需要着重指出的是，数组my_attrs的最后一项一定要赋为NULL，否则会造成内核oops。
*/
ssize_t my_show(struct kobject *kobj, struct attribute *attr, char *buffer)
{
	struct my_kobj *obj = container_of(kobj, struct my_kobj, kobj);
	ssize_t count = 0;

	if (strcmp(attr->name, "name") == 0) {
		count = sprintf(buffer, "%s\n", kobject_name(kobj));
	} else if (strcmp(attr->name, "val") == 0) {
		count = sprintf(buffer, "%d\n", obj->val);
	}

	return count;
}

ssize_t my_store(struct kobject *kobj, struct attribute *attr, const char *buffer, size_t size)
{
	struct my_kobj *obj = container_of(kobj, struct my_kobj, kobj);

	if (strcmp(attr->name, "val") == 0) {
		sscanf(buffer, "%d", &obj->val);
	}

	return size;
}

struct sysfs_ops my_sysfsops = {
	.show = my_show,
	.store = my_store,
};

void obj_release(struct kobject *kobj)
{
	struct my_kobj *obj = container_of(kobj, struct my_kobj, kobj);
	printk(KERN_INFO "obj_release %s\n", kobject_name(&obj->kobj));
	kfree(obj);
}

static int __init mykobj_init(void)
{
	printk(KERN_INFO "mykobj_init\n");

	obj1 = kzalloc(sizeof(struct my_kobj), GFP_KERNEL); //分配obj1和obj2并赋值
	if (!obj1) {
		return -ENOMEM;
	}
	obj1->val = 1;

	obj2 = kzalloc(sizeof(struct my_kobj), GFP_KERNEL);
	if (!obj2) {
		kfree(obj1);
		return -ENOMEM;
	}
	obj2->val = 2;

	my_type.release = obj_release;
	my_type.default_attrs = my_attrs;
	my_type.sysfs_ops = &my_sysfsops;

	kobject_init_and_add(&obj1->kobj, &my_type, NULL, "mykobj1"); /*函数来初始化kobject并把它加入到设备模型的体系架构*/
	kobject_init_and_add(&obj2->kobj, &my_type, &obj1->kobj, "mykobj2");
/*
	kobject_init用来初始化kobject结构，kobject_add用来把kobj加入到设备模型之中。
	在实作中，我们先对obj1进行初始化和添加的动作，调用参数里，parent被赋为NULL，表示obj1没有父对象，反映到sysfs里，
	my_kobj1的目录会出现在/sys下，obj2的父对象设定为obj1，那么my_kobj2的目录会出现在/sys/my_kobj1下面。
	前面提到，kobject也提供了引用计数的功能，虽然本质上是利用kref，但也提供了另外的接口供用户使用。
	kobject_init_and_add和kobject_init这两个函数被调用后，kobj的引用计数会初始化为1，
	所以在module_exit时要记得用kobject_put来释放引用计数。
*/	
	return 0;
}

static void __exit mykobj_exit(void)
{
	printk(KERN_INFO "mykobj_exit\n");

	kobject_del(&obj2->kobj); /*先子对象，后父对象*/
	kobject_put(&obj2->kobj);
	
	kobject_del(&obj1->kobj);
	kobject_put(&obj1->kobj);

	return;
}
/*
kobject_del的作用是把kobject从设备模型的那棵树里摘掉，同时sysfs里相应的目录也会删除。
这里需要指出的是，释放的顺序应该是先子对象，后父对象。
因为kobject_init_and_add和kobject_add这两个函数会调用kobject_get来增加父对象的引用计数，
所以kobject_del需要调用kobject_put来减少父对象的引用计数。在本例中，如果先通过kobject_put来释放obj1，
那kobject_del(&obj2->kobj)就会出现内存错误。
*/
module_init(mykobj_init);
module_exit(mykobj_exit);

MODULE_LICENSE("GPL");
